﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using GoldDigger.Data;
using GoldDigger.Processor;
using GoldDigger.Models;
using System.Data;

namespace GoldDigger.AppServices
{
    public class MainService
    {
        public DataHolder AppData { get; private set; }
        public ProblemProcessor AppProcessor { get; private set; }
        public CallSourceEnum.CallSource CommandCallSource { get; set; }

        public MainService()
        {
            AppData = new DataHolder();
            AppProcessor = new ProblemProcessor(AppData);
        }

        public void ImportSource(SourceFileModel sf, bool control, Action<bool> callback)
        {
            AppData.SourceDT = AppProcessor.PedMapToDT(sf, control);
            callback(true);
        }
        
        public void RunFcbf(string mode, double modeVal, Action<bool> callback)
        {
            var runMode = mode;
            var mv = modeVal;
            var tmpSu = 0d;
            
            AppData.AttributeSymmetricalUncertainty = new List<SuValueModel>();

            foreach (var a in AppData.AttributeList)
            {
                tmpSu = AppProcessor.CalculateSU(a.AttributeName, "Category");
                if (runMode == "Threshold")
                {
                    if (tmpSu >= modeVal)
                        AppData.AttributeSymmetricalUncertainty.Add((new SuValueModel((a.AttributeName), tmpSu)));
                }
                else
                {
                    AppData.AttributeSymmetricalUncertainty.Add(new SuValueModel((a.AttributeName), tmpSu));
                }
            }

            //order SU list
            AppData.AttributeSymmetricalUncertainty = AppData.AttributeSymmetricalUncertainty.OrderByDescending(u => u.SuValue).ToList();

            //new list to work on cloned from app data list
            var workingList = new List<SuValueModel>(AppData.AttributeSymmetricalUncertainty);

            //Get first value in lsit
            var suFirstModel = workingList[0];
            workingList.RemoveAt(0);

            while(suFirstModel != null)
            {
                //Copy first list so we can iterate through remaining values without ruining algorithm
                var workingList2 = new List<SuValueModel>(workingList);

                var suSecondModel = workingList2[0];
                workingList2.RemoveAt(0);

                while(suSecondModel != null)
                {
                    var suValBetweenModels =
                        AppProcessor.CalculateSU(suFirstModel.AttributeName, suSecondModel.AttributeName);

                    if (suValBetweenModels >= suSecondModel.SuValue)
                    {
                        AppData.AttributeSymmetricalUncertainty.Remove(suSecondModel);
                        workingList.Remove(suSecondModel);
                    }

                    if (workingList2.Count > 0)
                    {
                        suSecondModel = workingList2[0];
                        workingList2.RemoveAt(0);
                    }
                    else
                    {
                        suSecondModel = null;
                    }
                }

                if (workingList.Count > 0)
                {
                    suFirstModel = workingList[0];
                    workingList.RemoveAt(0);
                }
                else
                {
                    suFirstModel = null;
                }
            }

            //If AttributeSymmetrical Uncertainty list is longer than subset size x then trim
            if (runMode == "SubsetSize")
            {
                var tmpList = new List<SuValueModel>();
                var counter = (int)modeVal;
                //Check if list is shorter than specified subset size
                //Should raise notification here if so
                if (counter > AppData.AttributeSymmetricalUncertainty.Count)
                    counter = AppData.AttributeSymmetricalUncertainty.Count;

                for (int i = 0; i < counter; i++)
                    tmpList.Add(AppData.AttributeSymmetricalUncertainty[i]);
                AppData.AttributeSymmetricalUncertainty = tmpList;
            }

            var colsToKeep = new List<string>();
            var reducedList = new List<AttributeModel>();

            foreach (var o in AppData.AttributeSymmetricalUncertainty)
            {
                colsToKeep.Add(o.AttributeName);
                reducedList.Add(new AttributeModel(o.AttributeName, false));
            }

            //Use AttributeSymmetrical Uncertainty to update Attribute list and thus working datatable
            var tmpDT = new DataTable();
            tmpDT = AppData.SourceDT.Copy();

            foreach (var o in AppData.AttributeList)
            {
                if (!(colsToKeep.Contains(o.AttributeName)))
                {
                    tmpDT.Columns.Remove(o.AttributeName);
                }
            }

            AppData.ReducedFeatureDT = tmpDT;
            AppData.ReducedAttributeList = reducedList;
            callback(true);
         }

        public void RunApriori(double conf, double support, Action<bool> callback)
        {
            AppData.AprioriData.Confidence = conf;
            AppData.AprioriData.Support = support;

            var candidateItemset = new List<ItemsetModel>();
            var seedItemset = new List<ItemsetModel>();

            if (CommandCallSource == CallSourceEnum.CallSource.Full)
                AppData.CurrentlySelectedDT = AppData.SourceDT;
            else
                AppData.CurrentlySelectedDT = AppData.ReducedFeatureDT;

            //Get all attribute values and convert them to the a list of strings
            //With the format "SNP Name + genotype value"
            foreach (DataRow dr in AppData.CurrentlySelectedDT.Rows)
            {
                var myList = new List<string>();

                for (int i = 1; i < AppData.CurrentlySelectedDT.Columns.Count; i++)
                {
                    var attributeName = AppData.CurrentlySelectedDT.Columns[i].ColumnName + " " + dr[i].ToString();
                    myList.Add(attributeName);
                }

                AppData.AprioriData.DataTableStringList.Add(myList);
            }

            AppProcessor.GenerateLarge1Itemsets();

            //Main body of algorithm
            int k = 2;
            seedItemset = AppProcessor.GetCandidateSeeds(AppData.AprioriData.LargeItemsets, (k-1));

            //Continue generating candidate itemsets until they become too large
            //Any candidate sets which are large, add these to the list
            while (seedItemset.Count != 0)
            {
                candidateItemset = AppProcessor.GetCandidateItems(seedItemset);

                foreach (ItemsetModel s in candidateItemset)
                {
                    if (!s.In(AppData.AprioriData.LargeItemsets) && !s.In(AppData.AprioriData.NonLargeItemsets))
                    {
                        AppProcessor.CalculateSupport(s);

                        if (s.SupportVal >= AppData.AprioriData.Support)
                            AppData.AprioriData.LargeItemsets.Add(s);
                        else
                            AppData.AprioriData.NonLargeItemsets.Add(s);
                    }
                }

                k++;
                seedItemset = AppProcessor.GetCandidateSeeds(AppData.AprioriData.LargeItemsets, (k - 1));
            }
            
            //Now that all large itemsets have been found generate association rules from this
            var ruleSource = (from im in AppData.AprioriData.LargeItemsets where im.ItemList.Count>1 select im).ToList();

            foreach(ItemsetModel im in ruleSource)
            {
                for (int i = 0; i < im.ItemList.Count; i++)
                {
                    //set up right and left hand side of potential rules
                    var lhs = new ItemsetModel(new List<string>());
                    var rhs = new ItemsetModel(new List<string>());

                    //lhs equal to full set atm
                    lhs = im.Clone();

                    //rhs equal to last item in lhs
                    rhs.ItemList.Add(lhs.ItemList[lhs.ItemList.Count - 1]);
                    lhs.ItemList.RemoveAt(lhs.ItemList.Count - 1);

                    while (lhs.ItemList.Count != 0)
                    {
                        var c = AppProcessor.CalculateConfidence(rhs, im.SupportCount);

                        if (c >= AppData.AprioriData.Confidence)
                        {
                            var tmpLhs = lhs.Clone();
                            var tmpRhs = rhs.Clone();
                            AppData.AprioriData.AssociationRules.Add(new AssociationRuleModel(tmpLhs,tmpRhs,c)); 
                        }

                        rhs.ItemList.Add(lhs.ItemList[lhs.ItemList.Count - 1]);
                        lhs.ItemList.RemoveAt(lhs.ItemList.Count - 1);
                    }
                }
            }

            //Order rules by confidence values
            AppData.AprioriData.AssociationRules = AppData.AprioriData.AssociationRules.OrderByDescending(u => u.Confidence).ToList();

            callback(true);
        }

        public void ExportResultsToGexf(string path, AssociationRuleModel rule, Action<bool> callback)
        {
            AppProcessor.ExportResults(path, rule);
            callback(true);
        }
    }
}
